package io.github.jsp.lock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.sql.Timestamp;
import java.util.UUID;

@Component
public class DistributedLock {
    
    private static final Logger logger = LoggerFactory.getLogger(DistributedLock.class);
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    private static final String INSERT_LOCK_SQL = 
        "INSERT INTO sys_job_lock (lock_name, lock_value, expire_time) VALUES (?, ?, ?)";
    
    private static final String DELETE_LOCK_SQL = 
        "DELETE FROM sys_job_lock WHERE lock_name = ? AND lock_value = ?";
    
    private static final String UPDATE_LOCK_SQL = 
        "UPDATE sys_job_lock SET expire_time = ? WHERE lock_name = ? AND lock_value = ?";
    
    private static final String CLEAN_EXPIRED_LOCK_SQL = 
        "DELETE FROM sys_job_lock WHERE expire_time < CURRENT_TIMESTAMP";
    
    public boolean tryLock(String lockName, long timeoutMillis) {
        String lockValue = generateLockValue();
        Timestamp expireTime = new Timestamp(System.currentTimeMillis() + timeoutMillis);
        
        try {
            cleanExpiredLocks();
            
            int result = jdbcTemplate.update(INSERT_LOCK_SQL, lockName, lockValue, expireTime);
            
            if (result > 0) {
                logger.debug("Successfully acquired lock: {}, value: {}", lockName, lockValue);
                return true;
            }
        } catch (DuplicateKeyException e) {
            logger.debug("Failed to acquire lock: {} (already exists)", lockName);
        } catch (Exception e) {
            logger.error("Error acquiring lock: {}", lockName, e);
        }
        
        return false;
    }
    
    public boolean tryLockWithResult(String lockName, long timeoutMillis, LockResult lockResult) {
        String lockValue = generateLockValue();
        Timestamp expireTime = new Timestamp(System.currentTimeMillis() + timeoutMillis);
        
        try {
            cleanExpiredLocks();
            
            int result = jdbcTemplate.update(INSERT_LOCK_SQL, lockName, lockValue, expireTime);
            
            if (result > 0) {
                lockResult.setLockValue(lockValue);
                lockResult.setSuccess(true);
                logger.debug("Successfully acquired lock: {}, value: {}", lockName, lockValue);
                return true;
            }
        } catch (DuplicateKeyException e) {
            logger.debug("Failed to acquire lock: {} (already exists)", lockName);
        } catch (Exception e) {
            logger.error("Error acquiring lock: {}", lockName, e);
        }
        
        lockResult.setSuccess(false);
        return false;
    }
    
    public boolean unlock(String lockName, String lockValue) {
        try {
            int result = jdbcTemplate.update(DELETE_LOCK_SQL, lockName, lockValue);
            
            if (result > 0) {
                logger.debug("Successfully released lock: {}, value: {}", lockName, lockValue);
                return true;
            } else {
                logger.warn("Failed to release lock: {} (not found or different value)", lockName);
                return false;
            }
        } catch (Exception e) {
            logger.error("Error releasing lock: {}", lockName, e);
            return false;
        }
    }
    
    public boolean renewLock(String lockName, String lockValue, long timeoutMillis) {
        try {
            Timestamp expireTime = new Timestamp(System.currentTimeMillis() + timeoutMillis);
            int result = jdbcTemplate.update(UPDATE_LOCK_SQL, expireTime, lockName, lockValue);
            
            if (result > 0) {
                logger.debug("Successfully renewed lock: {}, value: {}", lockName, lockValue);
                return true;
            } else {
                logger.warn("Failed to renew lock: {} (not found or different value)", lockName);
                return false;
            }
        } catch (Exception e) {
            logger.error("Error renewing lock: {}", lockName, e);
            return false;
        }
    }
    
    private void cleanExpiredLocks() {
        try {
            int cleanedCount = jdbcTemplate.update(CLEAN_EXPIRED_LOCK_SQL);
            if (cleanedCount > 0) {
                logger.debug("Cleaned {} expired locks", cleanedCount);
            }
        } catch (Exception e) {
            logger.warn("Error cleaning expired locks", e);
        }
    }
    
    private String generateLockValue() {
        return UUID.randomUUID().toString().replace("-", "") + "_" + Thread.currentThread().getId();
    }
    
    public static class LockResult {
        private boolean success;
        private String lockValue;
        
        public boolean isSuccess() {
            return success;
        }
        
        public void setSuccess(boolean success) {
            this.success = success;
        }
        
        public String getLockValue() {
            return lockValue;
        }
        
        public void setLockValue(String lockValue) {
            this.lockValue = lockValue;
        }
    }
}